Skip to content

fix(test): replace Bun.sleep with utimes in dsn-cache mtime tests#612

Closed
betegon wants to merge 13 commits into
mainfrom
fix/dsn-cache-test-mtime-flakiness
Closed

fix(test): replace Bun.sleep with utimes in dsn-cache mtime tests#612
betegon wants to merge 13 commits into
mainfrom
fix/dsn-cache-test-mtime-flakiness

Conversation

@betegon
Copy link
Copy Markdown
Member

@betegon betegon commented Mar 31, 2026

Summary

  • Three mtime-based tests in test/lib/db/dsn-cache.test.ts were flaky on Linux CI: they called Bun.sleep(10) before writing a file, expecting the OS to advance the mtime. On Linux containers under load, mtime resolution can be coarser than the sleep delay, so the new and cached mtimes compared equal — making the cache appear valid when it shouldn't be.
  • Replaced the sleep approach with explicit utimes() calls that set the mtime to cachedMtime + 5000ms, guaranteeing a detectable difference regardless of OS clock resolution or scheduler jitter.
  • No production code changed — test-only fix.

Test plan

  • All 21 tests in test/lib/db/dsn-cache.test.ts pass locally
  • CI unit-test job passes without the three previously-flaky tests intermittently failing

🤖 Generated with Claude Code

Sleeping before a file write to force an mtime change is unreliable on
Linux CI — the OS mtime resolution can be coarser than the delay,
causing intermittent failures. Use utimes() instead to set the mtime to
a guaranteed-different value, making the tests deterministic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 31, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (auth) Enforce auth by default in buildCommand by betegon in #611
  • (skill) Add eval framework to measure SKILL.md effectiveness by BYK in #602
  • (telemetry) Add seer.outcome span tag for Seer command metrics by BYK in #609
  • (upgrade) Show changelog summary during CLI upgrade by BYK in #594

Bug Fixes 🐛

Test

  • Replace Bun.sleep with utimes in dsn-cache mtime tests by betegon in #612

Upgrade

  • Prevent spinner freeze during delta patch application by BYK in #608
  • Indent changelog, add emoji to heading, hide empty sections by BYK in #604

Other

  • (dashboard) Reject MRI queries with actionable tracemetrics guidance by BYK in #601
  • (init) Prompt/spinner ordering by betegon in #610
  • (skill) Avoid unnecessary auth, reinforce auto-detection, fix field examples by BYK in #599
  • 2 bug fixes — subcommand crash, negative span depth, pagination JSON parse by cursor in #607

Documentation 📚

  • (skill) Document dashboard widget constraints and deprecated datasets by BYK in #605
  • Fix documentation gaps and embed skill files at build time by cursor in #606

Internal Changes 🔧

  • Regenerate skill files and command docs by github-actions[bot] in 664362ca

🤖 This preview updates automatically when you update the PR.

betegon and others added 4 commits March 31, 2026 16:01
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The handler removal was inside if (client?.getOptions().enabled), so
calling initSentry(false) in test afterEach hooks never cleaned up the
handler registered by the prior initSentry(true) call. The stale handler
kept the event loop alive after all tests finished, causing the bun
process to hang indefinitely and the CI Unit Tests step to never complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…test

Same flakiness as dsn-cache: 10ms sleep is unreliable on Linux CI with
1-second filesystem mtime resolution. Use utimes() to set an explicit
future mtime instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@betegon betegon marked this pull request as ready for review March 31, 2026 15:32
betegon and others added 4 commits March 31, 2026 17:38
## Summary

All commands now require authentication by default. `buildCommand`
checks `isAuthenticated()` before invoking the command function and
throws `AuthError("not_authenticated")` if no token exists — this feeds
into the existing auto-auth middleware that prompts login and retries.

Previously each command had to manually guard auth or rely on an API
call failing deep in execution. `sentry init` in particular had no auth
check at all and would proceed into the wizard flow before eventually
failing.

## Changes

Added `auth?: boolean` to `buildCommand` options (defaults `true`). The
check runs before the generator, so the auto-auth middleware in `cli.ts`
catches it and kicks off the interactive login flow on unauthenticated
runs.

Commands that intentionally work without a token opt out with `auth:
false`:
- `auth login`, `auth logout`, `auth refresh`, `auth status`
- `help`, `schema`
- `cli fix`, `cli setup`, `cli upgrade`, `cli feedback`

With this in place, the redundant explicit `isAuthenticated()` checks in
`auth whoami` and `auth token` were removed.

## Test Plan

- `sentry init` (unauthenticated) → should immediately prompt
"Authentication required. Starting login flow..."
- `sentry auth login` (unauthenticated) → should work normally
- `sentry auth status` (unauthenticated) → should show "not
authenticated" cleanly, no login prompt
- `sentry auth logout` (unauthenticated) → should return "Not currently
authenticated."
- `SENTRY_AUTH_TOKEN=xxx sentry init` → no auth prompt, proceeds
normally

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread src/commands/auth/token.ts
getAuthConfigSpy = spyOn(dbAuth, "getAuthConfig").mockReturnValue({
token: "sntrys_test",
source: "config",
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock uses invalid AuthSource value "config"

Low Severity

The getAuthConfig mock returns source: "config", but the AuthSource type only allows "env:SENTRY_AUTH_TOKEN", "env:SENTRY_TOKEN", or "oauth". Every other test file in the codebase that mocks getAuthConfig uses a valid source like "oauth". While the auth guard only checks for truthiness today, if any downstream code ever pattern-matches on source (e.g., checking for ENV_SOURCE_PREFIX), this mock would silently produce wrong test behavior.

Fix in Cursor Fix in Web

betegon and others added 3 commits March 31, 2026 18:40
… stale beforeExit listeners

LightNodeClient.startClientReportTracking() and enableLogs both register
process.on('beforeExit', ...) handlers that are only removed by calling
client.close(). Without closing the previous client before Sentry.init(),
re-calling initSentry (e.g. initSentry(false) in test afterEach) accumulates
stale listeners that fire async work (HTTP sends) on beforeExit, preventing
the bun process from exiting after tests complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary

Fixes the ~200 test failures caused by the auth guard added in #611.
Meant to be merged into #612 so both the CI hang (dsn-cache timing +
telemetry beforeExit) and the auth guard breakage ship together.

## Problem

`buildCommand`'s new auth guard calls `getAuthConfig()` before every
command. Test environments had no auth token set, so all command tests
hit `AuthError("not_authenticated")` before the func body ran.

## Changes

**Global fix (test/preload.ts):**
- Set a fake `SENTRY_AUTH_TOKEN` in the test preload so the auth guard
passes for all tests. Real API calls are blocked by the global fetch
mock.

**Framework tests (test/lib/command.test.ts):**
- Add `auth: false` to all 29 test commands — these test flag handling,
telemetry, and output rendering, not authentication.

**Auth-specific tests (logout, refresh, whoami, project list):**
- Tests that verify unauthenticated behavior or `SENTRY_TOKEN` priority
now explicitly save/clear/restore `SENTRY_AUTH_TOKEN`.

## Test plan

- 215 tests across the 7 most-affected files pass with 0 failures
- `bun test test/commands` — 1209 pass
- Lint and typecheck pass

---------

Co-authored-by: Miguel Betegón <miguelbetegongarcia@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… tests

- log/list.test.ts: Mock resolveOrgProjectFromArg in 'allows --sort newest
  with --follow' test to prevent unmocked fetch call that triggered retry
  timeouts

- wizard-runner.test.ts: Add default org option to skip resolveOrgSlug
  which calls listOrganizations API
betegon added a commit that referenced this pull request Mar 31, 2026
)

## Summary

Fixes the CI unit test hang and ~200 test failures caused by three
merged PRs (#610, #611). Supersedes #612 with a cleaner approach.

### 1. CI Hang — telemetry `beforeExit` handler leak

`initSentry(false)` in test `afterEach` hooks never cleaned up the
`beforeExit` handler registered by a prior `initSentry(true)` call — the
removal was gated on `client?.getOptions().enabled`. SDK-internal
handlers from `enableLogs` and `sendClientReports` also accumulated on
re-init.

**Fix** (src/lib/telemetry.ts):
- Close previous Sentry client before re-init
(`Sentry.getClient()?.close(0)`)
- Move handler removal outside the `enabled` guard
- Gate `enableLogs` and `sendClientReports` on `enabled` (not just
`libraryMode`)
- Snapshot and clean up `ProcessSession` `beforeExit` listeners in
telemetry.test.ts

### 2. Auth guard test failures (PR #611)

`buildCommand` now calls `getAuthConfig()` before every command. Tests
had no auth token.

**Fix**:
- Set fake `SENTRY_AUTH_TOKEN` in test/preload.ts (blocked by global
fetch mock)
- Add `auth: false` to all 34 `buildCommand` calls in command.test.ts
- Save/clear/restore `SENTRY_AUTH_TOKEN` in 9 test files that verify
unauthenticated behavior
- Add `getAuthConfig` mock + `resolveOrgProjectFromArg` mock in log/list
tests

### 3. Dead code tests (PR #610)

PR #610 moved org resolution and DSN detection from
`createSentryProject` to `wizard-runner`'s `resolvePreSpinnerOptions`.
Tests that exercised those flows through `handleLocalOp` hit the new
`!options.org` guard.

**Fix**: Rewrote 15 tests to call `resolveOrgSlug` and
`detectExistingProject` directly, plus 2 new tests for
`createSentryProject`'s existing-project check. No test coverage lost.

### 4. Mtime test flakiness

Replaced `Bun.sleep(10)` with explicit `utimes()` calls in dsn-cache and
project-root-cache tests for deterministic mtime differences on Linux
CI.

## Test plan

- `bun test` — 630 pass, 4 fail (pre-existing: 3 resolve-target timeouts
from unmocked fetch, 1 log/list failure from missing generated
api-schema.json)
- `bun run lint` — clean (1 pre-existing warning)

Made with [Cursor](https://cursor.com)
Remove the SENTRY_AUTH_TOKEN env var from test/preload.ts and replace
it with explicit getAuthConfig mocks via a new useAuthMock() helper
in test/helpers.ts. This is cleaner than the env var approach because:

- Mocks are visible in each test file (no hidden global state)
- Tests that need unauthenticated behavior work naturally
- No env var save/delete/restore boilerplate needed in auth tests
- Doesn't change the auth code path (env token vs DB token)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

There are 5 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

// Standard mode (project-scoped, no trace-id positional)
// ============================================================================

useAuthMock();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate auth spies create brittle tests

Medium Severity

test/commands/log/list.test.ts now mocks dbAuth.getAuthConfig twice per test via both useAuthMock() and a manual beforeEach spy. This double wrapping can make mockRestore() ordering fragile and can fail in runners that disallow spying an already-spied method, causing intermittent or cascading test failures.

Additional Locations (1)
Fix in Cursor Fix in Web

token: "fake-token",
source: "oauth" as const,
});
spyOn(auth, "isAuthenticated").mockReturnValue(true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth spies leak across wizard tests

Medium Severity

test/lib/init/wizard-runner.test.ts adds spyOn(auth, "getAuthConfig") and spyOn(auth, "isAuthenticated") in beforeEach but never restores them in afterEach. That leaves mocked auth state active between tests and can cause order-dependent failures or hidden regressions.

Fix in Cursor Fix in Web

Comment thread src/lib/telemetry.ts
// this, re-initializing the SDK (e.g., in tests) leaks setInterval handles
// that keep the event loop alive and prevent the process from exiting.
// close(0) removes listeners synchronously; we don't need to await the flush.
Sentry.getClient()?.close(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telemetry init closes host Sentry client

High Severity

initSentry() now calls Sentry.getClient()?.close(0) before every init. In library usage, this can close an already-initialized host application's Sentry client, not just this module's previous client, unexpectedly disabling host telemetry and mutating global process instrumentation.

Fix in Cursor Fix in Web

listener as (...args: unknown[]) => void
);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global listener cleanup can remove unrelated hooks

Medium Severity

The new afterAll removes every beforeExit listener not present at file load. In shared-process test execution, that can delete listeners registered by other suites during runtime, causing cross-test interference and nondeterministic failures.

Fix in Cursor Fix in Web

@betegon betegon closed this Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants